package com.ymwk.sm4;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

import com.ymwk.common.util.Base64Utils;

public class Sm4Utils {
	public static final String ALGORITHM_NAME = "SM4";
	public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
	public static final String ALGORITHM_NAME_ECB_NOPADDING = "SM4/ECB/NoPadding";
	public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
	public static final String ALGORITHM_NAME_CBC_NOPADDING = "SM4/CBC/NoPadding";

	/**
	 * SM4算法目前只支持128位（即密钥16字节）
	 */
	public static final int DEFAULT_KEY_SIZE = 128;

	public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
		return generateKey(DEFAULT_KEY_SIZE);
	}

	public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
		KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
		kg.init(keySize, new SecureRandom());
		return kg.generateKey().getEncoded();
	}

	public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
		return cipher.doFinal(data);
	}

	public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText)
			throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
		return cipher.doFinal(cipherText);
	}

	public static boolean encrypt_ECB_Padding(byte[] key, String orgPath, String encryptPath)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException, IOException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
		// 定义输入流
		InputStream in = new FileInputStream(orgPath);
		// 定义输出流
		OutputStream out = new FileOutputStream(encryptPath);
		CipherOutputStream cipherOut = new CipherOutputStream(out, cipher);
		IOUtils.copy(in, cipherOut);
		cipherOut.close();
		in.close();
		out.close();
		return true;
	}

	public static boolean encrypt_ECB_Padding(byte[] key, InputStream in, String encryptPath)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException, IOException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
		// 定义输出流
		OutputStream out = new FileOutputStream(encryptPath);
		CipherOutputStream cipherOut = new CipherOutputStream(out, cipher);
		IOUtils.copy(in, cipherOut);
		cipherOut.close();
		in.close();
		out.close();
		return true;
	}

	public static boolean decrypt_ECB_Padding(byte[] key, String encryptPath, String DecryptPath)
			throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException, IOException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
		// 定义输入流
		InputStream in = new FileInputStream(encryptPath);
		// 定义输出流
		OutputStream out = new FileOutputStream(DecryptPath);
		CipherOutputStream cipherOut = new CipherOutputStream(out, cipher);
		IOUtils.copy(in, cipherOut);
		cipherOut.close();
		in.close();
		out.close();
		return true;
	}

	public static boolean decrypt_ECB_Padding(byte[] key, InputStream in, String DecryptPath)
			throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException, IOException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
		// 定义输出流
		OutputStream out = new FileOutputStream(DecryptPath);
		CipherOutputStream cipherOut = new CipherOutputStream(out, cipher);
		IOUtils.copy(in, cipherOut);
		cipherOut.close();
		in.close();
		out.close();
		return true;
	}

	public static byte[] encrypt_ECB_NoPadding(byte[] key, byte[] data)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.ENCRYPT_MODE, key);
		return cipher.doFinal(data);
	}

	public static byte[] decrypt_ECB_NoPadding(byte[] key, byte[] cipherText)
			throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException {
		Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.DECRYPT_MODE, key);
		return cipher.doFinal(cipherText);
	}

	public static byte[] encrypt_CBC_Padding(byte[] key, byte[] iv, byte[] data)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
		Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
		return cipher.doFinal(data);
	}

	public static byte[] decrypt_CBC_Padding(byte[] key, byte[] iv, byte[] cipherText)
			throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException {
		Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
		return cipher.doFinal(cipherText);
	}

	public static byte[] encrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] data)
			throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
			IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
		Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.ENCRYPT_MODE, key, iv);
		return cipher.doFinal(data);
	}

	public static byte[] decrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] cipherText)
			throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException {
		Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.DECRYPT_MODE, key, iv);
		return cipher.doFinal(cipherText);
	}

	private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
			throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException {
		Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
		Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
		cipher.init(mode, sm4Key);
		return cipher;
	}

	private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
			throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException {
		Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
		Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
		IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
		cipher.init(mode, sm4Key, ivParameterSpec);
		return cipher;
	}

	public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException,
			NoSuchProviderException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		String orgPath = "D:\\java_work_space\\JpaDemo\\target\\classes\\com\\ymwk\\util\\Base64Utils.class";
		String encryptPath = "D:\\java_work_space\\JpaDemo\\target\\classes\\com\\ymwk\\util\\Base64Utils1.class";
		String decryptPath = "C:\\Users\\Saxon\\Desktop\\test-decryptAfter.txt";
		Security.addProvider(new BouncyCastleProvider());
    	byte[] generateKey = generateKey();
    	System.out.println(Base64Utils.encode(generateKey));
//    	System.out.println(Hex.toHexString(generateKey));
		//byte[] generateKey = Hex.decodeStrict("04f21a46d45b14451d0f470893de58af");

		boolean encrypt_ECB_Padding = encrypt_ECB_Padding(generateKey, orgPath, encryptPath);

//    	System.out.println(encrypt_ECB_Padding);
		byte[] decryptBytes = decrypt_ECB_Padding(generateKey, FileUtils.readFileToByteArray(new File(encryptPath)));
		System.out.println(Hex.toHexString(decryptBytes));
		/*
		 * FileOutputStream decryptOut = new FileOutputStream(decryptPath);
		 * IOUtils.copy(new ByteArrayInputStream(decryptBytes), decryptOut);
		 * //encryptOut.close(); decryptOut.close();
		 */
		System.out.println("完成");

	}

}
